package com.lvy.hczn.front.business.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.lvy.hczn.front.business.common.CacheConstants;
import com.lvy.hczn.front.business.common.Constants;
import com.lvy.hczn.front.common.core.domain.entity.SysDictData;
import com.lvy.hczn.front.common.utils.DictUtils;
import com.lvy.hczn.front.system.domain.business.*;
import com.lvy.hczn.front.business.dto.Param;
import com.lvy.hczn.front.business.dto.SortTaskDto;
import com.lvy.hczn.front.business.mapper.SortTaskMapper;
import com.lvy.hczn.front.business.service.*;
import com.lvy.hczn.front.common.core.redis.RedisCache;
import com.lvy.hczn.front.common.exception.UtilException;
import com.lvy.hczn.front.system.domain.system.SysStudent;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

@Slf4j
@Service
@Transactional(rollbackFor = Exception.class)
public class SortTaskServiceImpl extends ServiceImpl<SortTaskMapper, SortTask> implements SortTaskService {

    @Autowired
    private SortTaskMapper sortTaskMapper;

    @Autowired
    private SortTaskDetailService sortTaskDetailService;

    @Autowired
    private GoodsBoxService goodsBoxService;

    @Autowired
    private GoodsBoxDetailService goodsBoxDetailService;

    @Autowired
    private WcsService wcsService;

    @Autowired
    private RedisCache redisCache;

    @Autowired
    private SaleOrderService saleOrderService;

    @Autowired
    private TransferOrderService transferOrderService;

    @Override
    @Transactional
    public synchronized void generateSortTask(List<SaleOrder> list, boolean beforeSort) {
        if (list != null && !list.isEmpty()) {
            list.forEach(saleOrder -> {
                //退货入库也会触发任务，这里再查一下
                SaleOrder saleOrderSale = saleOrderService.getOne(Wrappers.<SaleOrder>lambdaQuery().eq(SaleOrder::getSaleOrderCode, saleOrder.getSaleOrderCode()).eq(SaleOrder::getType, com.lvy.hczn.front.common.constant.Constants.SALE_ORDER_TYPE_ONE).eq(SaleOrder::getStatus, com.lvy.hczn.front.common.constant.Constants.SALE_ORDER_STATUS_ZERO));
                if (saleOrderSale != null && saleOrder.getList() != null && !saleOrder.getList().isEmpty()) {
                    SortTask sortTask = new SortTask();
                    sortTask.setWarehouseCode(saleOrder.getWarehouseCode());
                    sortTask.setAssociatedOrderCode(saleOrder.getSaleOrderCode());
                    sortTask.setDataType(com.lvy.hczn.front.common.constant.Constants.WAREHOUSING_ENTRY_DATA_TYPE_EIGHT);
                    sortTask.setOutboundOrderCode(saleOrder.getDeliveryCode());
                    sortTask.setTaskType(Constants.SORT_TASK_TYPE_ORDER);
                    if (beforeSort) {
                        sortTask.setStatus(Constants.SORT_TASK_STATUS_DONE);
                        sortTask.setRemark("商品分拣前退货");
                    } else {
                        sortTask.setStatus(Constants.SORT_TASK_STATUS_ING);
                    }
                    sortTask.setSourceId(Constants.SORT_TASK_SOURCE_OWN);
                    sortTask.setServiceCode(saleOrder.getServiceCode());
                    sortTask.setPublisher(saleOrder.getMemberCode());
                    if (super.save(sortTask)) {
                        sortTaskDetailService.initTaskDetailBySaleOrder(saleOrder, saleOrder.getList(), sortTask, beforeSort);
                    }
                }
                saleOrder.setStatus(com.lvy.hczn.front.common.constant.Constants.SALE_ORDER_STATUS_ONE);
                saleOrderService.updateById(saleOrder);
            });

        }
    }

    /**
     * 生成调拨分拣任务
     *
     * @param list
     */
    @Override
    @Transactional
    public void generateSortTaskByTransfer(List<TransferOrder> list) {
        if (list != null && !list.isEmpty()) {
            list.forEach(transferOrder -> {
                SortTask sortTask = new SortTask();
                sortTask.setWarehouseCode(transferOrder.getStartReqId());
                sortTask.setAssociatedOrderCode(transferOrder.getTransferCode());
                sortTask.setDataType(com.lvy.hczn.front.common.constant.Constants.WAREHOUSING_ENTRY_DATA_TYPE_NINE);
                sortTask.setOutboundOrderCode(transferOrder.getTransferCode());
                sortTask.setTaskType(Constants.SORT_TASK_TYPE_ORDER);
                sortTask.setStatus(Constants.SORT_TASK_STATUS_ING);
                sortTask.setSourceId(Constants.SORT_TASK_SOURCE_OWN);
                sortTask.setServiceCode(transferOrder.getTaskCode());
                sortTask.setType(Constants.SORT_TASK_DATA_TYPE_TWO);
                sortTask.setSortCode(transferOrder.getSortCode());
                sortTask.setPublisher(transferOrder.getArriveReqId());
                sortTask.setEnterpriseId(transferOrder.getEnterpriseId());
                if (super.save(sortTask)) {
                    if(CollUtil.isNotEmpty(transferOrder.getDetailList())){
                        sortTaskDetailService.initTaskDetailByTransfer(transferOrder, transferOrder.getDetailList(), sortTask);
                    }
                    if(CollUtil.isNotEmpty(transferOrder.getParcelList())){
                        sortTaskDetailService.initTaskDetailByTransferParcel(transferOrder, transferOrder.getParcelList(), sortTask);
                    }
                    transferOrder.setStatus(com.lvy.hczn.front.common.constant.Constants.TRANSFER_ORDER_STATUS_FIVE);
                    transferOrderService.updateById(transferOrder);
                }
            });
        }
    }

    /**
     * 根据分拣员下架分拣原料箱
     *
     * @param clientId
     * @param status
     * @return
     * @author yw
     * @date 2022-04-17 14:56:18
     */
    @Override
    public GoodsBox sortBySpu(String clientId, String status) throws Exception {
        if (StrUtil.isEmpty(clientId)) {
            throw new UtilException("参数为空");
        }
        if (StrUtil.isEmpty(status)) {
            status = Constants.SORT_TASK_DETAIL_ING;
        }
        //分拣员待分拣的原料列表
        List<String> list = sortTaskDetailService.selectSortSpuIdByClient(clientId, status);
        if (!list.isEmpty()) {
            for (int i = 0; i < list.size(); i++) {
                //根据spuId获取第一份原料的货箱及详情
                GoodsBox goodsBox = goodsBoxService.sortByClient(list.get(i));
                if (goodsBox != null) {
                    return goodsBox;
                }
            }
        }
        return null;
    }

    /**
     * 获取去重的货箱编号
     * 根据需分拣的spu获取货箱详情
     * 根据需分拣的spu重量筛选货箱详情
     *
     * @param clientId
     * @param status
     * @return
     * @author yw
     * @date 2022-04-17 14:56:18
     */
    @Override
    public List<GoodsBoxDetail> selectUniqueBoxCodeBySpuList(String clientId, String status, SortTaskDto sortTaskDto) throws Exception {
        /*if (StrUtil.isEmpty(clientId)) {
            throw new UtilException("参数为空");
        }*/
        //分拣列表存在，则查询
        if (sortTaskDto != null && sortTaskDto.getReSortDetailList() != null && !sortTaskDto.getReSortDetailList().isEmpty()) {
            //分拣员待分拣的原料列表
            List<String> list = sortTaskDto.getReSortDetailList().stream().map(SortTaskDetail::getSpuId).distinct().collect(Collectors.toList());
            if (list.isEmpty()) {
                throw new UtilException("暂无分拣原料");
            }
            //获取符合条件的货箱详情
            //每个货箱可能有多包相同品类的物品，计算同一货箱相同品类的多条详情的总重量，然后再根据条件，如温区，保质期等去分组排序排序
            List<GoodsBoxDetail> boxDetailList = goodsBoxDetailService.selectBySpuIdList(list, clientId);
            List<GoodsBoxDetail> downloadBoxList = new ArrayList<>();
            if (!boxDetailList.isEmpty()) {
                Map<String, List<SortTaskDetail>> mapBySortDetailId = new HashMap<>();
                if (sortTaskDto.getSortDetailList() != null && !sortTaskDto.getSortDetailList().isEmpty()) {
                    mapBySortDetailId = sortTaskDto.getSortDetailList().stream().collect(Collectors.groupingBy(item -> item.getId()));
                }

                /**
                 *
                 * 分拣出库时，一次性获取全部包含需分拣spu货箱，然后货箱详情根据货箱分组，分拣任务根据spu分组
                 * 分拣详情与单个货箱循环处理
                 * 注：分拣详情不以spu，来区分总重量作为分组，目前考虑是单个详情，分拣够的情况，需更改详情状态，干脆循坏处理
                 *    先观察，若是嵌套循环太多，后期影响效率，再优化
                 * **/
                LinkedHashMap<String, List<GoodsBoxDetail>> goodsDetailMap = boxDetailList.stream().collect(Collectors.groupingBy(GoodsBoxDetail::getBoxCode, LinkedHashMap::new, Collectors.toList()));

//                LinkedHashMap<String, List<GoodsBoxDetail>> goodsDetailSpuMap = boxDetailList.stream().collect(Collectors.groupingBy(GoodsBoxDetail::getSpuId, LinkedHashMap::new, Collectors.toList()));

                LinkedHashMap<String, List<SortTaskDetail>> sortDetailSpuMap = sortTaskDto.getReSortDetailList().stream().collect(Collectors.groupingBy(SortTaskDetail::getSkuCode, LinkedHashMap::new, Collectors.toList()));

                //boxDetailList 的spuId按顺序处理不用sortTaskDto.getReSortDetailList()的spu去循环，不然会打乱spu顺序
                //必须按照boxDetailList查询顺序校验和出库，防止先出库其他货箱spu会有重复出货箱问题，先出库原料少的货箱
                //sortDetailSpuMap的spu不在goodsDetailSpuMap 的spu中，认为缺货
                Map<String, BigDecimal> weightMap = new HashMap<>();
                for (Map.Entry<String, List<GoodsBoxDetail>> goodsDetailEntry : goodsDetailMap.entrySet()) {
                    //货箱详情
                    for (int i = 0; i < goodsDetailEntry.getValue().size(); i++) {
                        GoodsBoxDetail goodsBoxDetail = goodsDetailEntry.getValue().get(i);
                        if (goodsBoxDetail.getWeight().compareTo(BigDecimal.ZERO) > 0) {
                            //根据spu获取相同spu分拣任务列表
                            List<SortTaskDetail> sortTaskDetailList = sortDetailSpuMap.get(goodsBoxDetail.getSkuCode());
                            if (sortTaskDetailList != null && !sortTaskDetailList.isEmpty()) {
                                for (int j = 0; j < sortTaskDetailList.size(); j++) {
                                    SortTaskDetail sortTaskDetail = sortTaskDetailList.get(j);
                                    if (sortTaskDetail.getSkuCode().equals(goodsBoxDetail.getSkuCode())) {
                                        if (!sortTaskDetail.getStatus().equals(Constants.SORT_TASK_DETAIL_ING) && goodsBoxDetail.getWeight().compareTo(BigDecimal.ZERO) > 0) {
                                            if (!mapBySortDetailId.containsKey(sortTaskDetail.getId())) {
                                                sortTaskDto.getSortDetailList().add(sortTaskDetail);
                                            }
                                            //跳出货箱分组循环，即货箱能满足当前分拣任务的分拣重量事，跳出货箱分组和货箱详情循环，处理下一个分拣任务
                                            BigDecimal sortWeight = BigDecimal.valueOf(sortTaskDetail.getCountQuantity()).multiply(sortTaskDetail.getWeight()).subtract(sortTaskDetail.getSortWeight());
                                            /*if (weightMap.containsKey(sortTaskDetail.getSkuCode())) {
                                                sortWeight = sortWeight.add(weightMap.get(sortTaskDetail.getSkuCode()));
                                            }*/
                                            //货箱详情分拣后，需分拣重量剩余重量
                                            sortWeight = filterBox(downloadBoxList, sortWeight, goodsBoxDetail, clientId, sortTaskDetailList.get(j));
                                            goodsDetailEntry.getValue().get(i).setWeight(goodsBoxDetail.getWeight());
                                            if (sortWeight == null) {
                                                weightMap.remove(sortTaskDetail.getSkuCode());
                                                //仓库内货箱能分拣完成 需分拣重量，则置为分拣中，会存在仓库物品不够的情况下，未置成分级按中
                                                /**设置为分拣中，且返回web**/
                                                sortTaskDetailList.get(j).setStatus(Constants.SORT_TASK_DETAIL_ING);
                                                sortTaskDetailService.updateById(sortTaskDetail);
                                            }/* else {
                                                //当前货箱详情未能满足分拣任务所需重量,到下一个货箱详情或者下一个货箱去累加计算
                                                if (weightMap.containsKey(sortTaskDetail.getSkuCode())) {
                                                    BigDecimal weight = sortWeight.add(weightMap.get(sortTaskDetail.getSkuCode()));
                                                    weightMap.put(sortTaskDetail.getSkuCode(), weight);
                                                } else {
                                                    weightMap.put(sortTaskDetail.getSkuCode(), sortWeight);
                                                }
                                            }*/
                                        }
                                    }
                                }
                                sortDetailSpuMap.remove(goodsBoxDetail.getSkuCode());
                                sortDetailSpuMap.put(goodsBoxDetail.getSkuCode(), sortTaskDetailList);
                            }
                        }
                    }
                }
                //第一版
                /*for (Map.Entry<String, List<GoodsBoxDetail>> detail : goodsDetailSpuMap.entrySet()) {
                    List<SortTaskDetail> sortTaskDetailList = sortDetailSpuMap.get(detail.getKey());
                    if (sortTaskDetailList != null && !sortTaskDetailList.isEmpty()) {
                        for (int j = 0; j < sortTaskDetailList.size(); j++) {
                            SortTaskDetail sortTaskDetail = sortTaskDetailList.get(j);
                            if (!mapBySortDetailId.containsKey(sortTaskDetail.getId())) {
                                sortTaskDto.getSortDetailList().add(sortTaskDetail);
                            }
                            BigDecimal sortWeight = BigDecimal.valueOf(sortTaskDetail.getQuantity()).multiply(sortTaskDetail.getWeight()).subtract(sortTaskDetail.getSortWeight());
                            //跳出货箱分组循环，即货箱能满足当前分拣任务的分拣重量事，跳出货箱分组和货箱详情循环，处理下一个分拣任务
                            boolean flag = false;
                            if (!sortTaskDetail.getStatus().equals(MqConstants.SORT_TASK_DETAIL_ING)) {
                                for (Map.Entry<String, List<GoodsBoxDetail>> entry : goodsDetailMap.entrySet()) {
                                    for (int i = 0; i < entry.getValue().size(); i++) {
                                        if (!sortTaskDetail.getStatus().equals(MqConstants.SORT_TASK_DETAIL_ING)) {
                                            GoodsBoxDetail goodsBoxDetail = entry.getValue().get(i);
                                            if (sortTaskDetail.getSpuId().equals(goodsBoxDetail.getSpuId()) && goodsBoxDetail.getWeight().compareTo(BigDecimal.ZERO) > 0) {
                                                if (sortWeight != null && sortWeight.compareTo(BigDecimal.ZERO) > 0) {
                                                    //货箱详情分拣后，需分拣重量剩余重量
                                                    sortWeight = filterBox(downloadBoxList, sortWeight, goodsBoxDetail, clientId);
                                                    entry.getValue().get(i).setWeight(goodsBoxDetail.getWeight());
                                                    if (sortWeight == null) {
                                                        //仓库内货箱能分拣完成 需分拣重量，则置为分拣中
                                                        *设置为分拣中，且返回web*
                                                        sortTaskDetail.setStatus(MqConstants.SORT_TASK_DETAIL_ING);
                                                        sortTaskDetailService.updateById(sortTaskDetail);
                                                        flag = true;
                                                        break;
                                                    }
                                                }
                                            }
                                        }
                                    }
                                    if (flag) {
                                        break;
                                    }
                                }
                            } else {
                                break;
                            }
                        }
                    }
                }*/

                log.error("出库货箱：" + JSONUtil.toJsonStr(downloadBoxList));
                //boxCode 去重
//                LinkedHashMap<Object, Boolean> map = new LinkedHashMap<>();
//                List<GoodsBoxDetail> goodsBoxDetails = downloadBoxList.stream().filter(i -> map.putIfAbsent(i.getBoxCode(), Boolean.TRUE) == null).collect(Collectors.toList());

                LinkedHashMap<String, List<GoodsBoxDetail>> map = downloadBoxList.stream().collect(Collectors.groupingBy(GoodsBoxDetail::getBoxCode, LinkedHashMap::new, Collectors.toList()));
                log.error("出库货箱排序：{}", JSONUtil.toJsonStr(map));
                // 同一品类，先出库一个，分拣完成发送指令入库离开缓存位，或者货箱出库指令后，再下第二个,
                //  缘由：多仓库协同时，wcs无法按发过去的顺序出库，有可能先出库重量多的货箱，所以，只能先发量少且快过期的
                //  离开缓存位响应的时候，再出库一个同品类的
//                LinkedHashMap<String, List<GoodsBoxDetail>> mapBySpu = goodsBoxDetails.stream().collect(Collectors.groupingBy(GoodsBoxDetail::getSpuId, LinkedHashMap::new, Collectors.toList()));
                List<GoodsBoxDetail> downloadList = new ArrayList<>();
                List<GoodsBoxDetail> lastDownloadList = new ArrayList<>();
                LinkedHashMap<String, Boolean> downloadMap = new LinkedHashMap<>();
                Map<Integer, Integer> warehouseMapInfo = new HashMap<>();
//                Map<String, String> lastBoxCodeMap = new HashMap<>();
//                Map<String, String> boxCodeMap = new HashMap<>();
                //一货箱多品类，出库货箱中含有此品类，则其他含有此品类的货箱等此货箱离开缓存位后再出库
                boolean download = false;
                for (Map.Entry<String, List<GoodsBoxDetail>> entry : map.entrySet()) {
                    List<GoodsBoxDetail> detailList = goodsDetailMap.get(entry.getKey());
                    GoodsBox goodsBox = goodsBoxService.getOne(Wrappers.<GoodsBox>lambdaQuery().eq(GoodsBox::getBoxCode, entry.getKey()));
                    for (GoodsBoxDetail goodsBoxDetailItem : detailList) {
                        goodsBoxDetailItem.setBoxStatus(goodsBox.getStatus());
                        goodsBoxDetailItem.setWarehouseNumber(goodsBox.getWarehouseNumber());
                        if (downloadMap.containsKey(goodsBoxDetailItem.getSpuId() + goodsBoxDetailItem.getSkuCode() + goodsBoxDetailItem.getSpecCode())) {
                            //不能break，等于true说明包含已经有包含这些spu的原料已出库，需要把同箱内其他spu put到downloadMap，不然会出库相同spu，会多出库
                            //即：相同的spu只能出库一个货箱，在途的货箱spu唯一，防止多箱重复出库，多仓库可能spu重量多的货箱先到位，则会多出库一个货箱
                            download = true;
                        } else {
                            downloadMap.put(goodsBoxDetailItem.getSpuId() + goodsBoxDetailItem.getSkuCode() + goodsBoxDetailItem.getSpecCode(), true);
                        }
                    }
                    //!downloadList.isEmpty()防止只有一个货箱的时候
                    if (download && !downloadList.isEmpty() && warehouseMapInfo.containsKey(goodsBox.getWarehouseNumber())) {
                        lastDownloadList.add(detailList.get(0));
                    } else {
                        if (detailList != null && !detailList.isEmpty()) {
                            warehouseMapInfo.put(goodsBox.getWarehouseNumber(), goodsBox.getWarehouseNumber());
                            downloadList.add(detailList.get(0));
                        }
                    }
                    /*for (GoodsBoxDetail goodsBoxDetail : goodsBox.getDetailList()) {
                        goodsBoxDetail.setBoxStatus(goodsBox.getStatus());
                        if (linkedHashMap.containsKey(goodsBoxDetail.getSpuId())) {
                            //正出库中含有此货箱则不放入待出库
                            if (!boxCodeMap.containsKey(goodsBoxDetail.getBoxCode())) {
                                lastBoxCodeMap.put(goodsBoxDetail.getBoxCode(), goodsBoxDetail.getBoxCode());
                                lastDownloadList.add(goodsBoxDetail);
                            }
                        } else {
                            //待出库中含有此货箱，则不放入正出库
                            if (!lastBoxCodeMap.containsKey(goodsBoxDetail.getBoxCode())) {
                                linkedHashMap.put(goodsBoxDetail.getSpuId(), true);
                                boxCodeMap.put(goodsBoxDetail.getBoxCode(), goodsBoxDetail.getBoxCode());
                                downloadList.add(goodsBoxDetail);
                            }
                        }
                    }*/
                }

                //保持顺序去重
                LinkedHashMap<Object, Boolean> map1 = new LinkedHashMap<>();
                List<GoodsBoxDetail> downloadListOfUnique = downloadList.stream().filter(i -> map1.putIfAbsent(i.getBoxCode(), Boolean.TRUE) == null).collect(Collectors.toList());

                //保持顺序去重
                LinkedHashMap<Object, Boolean> map2 = new LinkedHashMap<>();
                List<GoodsBoxDetail> lastDownloadListOfUnique = lastDownloadList.stream().filter(i -> map2.putIfAbsent(i.getBoxCode(), Boolean.TRUE) == null).collect(Collectors.toList());
                log.error("downloadList:" + JSONUtil.toJsonStr(downloadListOfUnique));
                log.error("lastDownloadList:" + JSONUtil.toJsonStr(lastDownloadListOfUnique));
                if (!lastDownloadListOfUnique.isEmpty()) {
                    //过滤已出库的,且未到缓存位的,且按仓库号分组
                    Map<Integer, List<GoodsBoxDetail>> numberList = lastDownloadListOfUnique.stream().filter(i -> i.getBoxStatus().equals(Constants.GOODS_BOX_STATE_THREE)).collect(Collectors.groupingBy(GoodsBoxDetail::getWarehouseNumber, LinkedHashMap::new, Collectors.toList()));
                    numberList.forEach((key, value) -> {
                        //温区排序，冷冻区后下
                        List<GoodsBoxDetail> detailListInfo = value.stream().sorted(Comparator.comparing(GoodsBoxDetail::getActTemperatureCode).reversed()).collect(Collectors.toList());
                        List<Object> objectList = JSON.parseArray(JSON.toJSONString(detailListInfo), Object.class);
                        redisCache.setAllListOfRight(CacheConstants.SORT_TASK_DOWNLOAD_LAST + key, objectList);
                    });
                }
                //虚拟货箱
                String vBoxCode = DictUtils.getDictValue(com.lvy.hczn.front.common.constant.Constants.VIRTUALLY_BOX_GOODS, com.lvy.hczn.front.common.constant.Constants.VIRTUALLY_BOX_GOODS_BOX_CODE);
                //过滤已出库的,且未到缓存位的
                return downloadListOfUnique.stream().filter(i -> i.getBoxStatus().equals(Constants.GOODS_BOX_STATE_THREE) && !i.getBoxCode().equals(vBoxCode)).collect(Collectors.toList());
            }
        }
        return null;
    }

    /**
     * 分拣重量与货箱重量比较
     *
     * @param downloadBoxList
     * @param sortWeight
     * @param goodsBoxDetail
     * @return java.math.BigDecimal
     * @author yw
     * @date 2022-07-10 10:03:01
     */
    @Nullable
    private BigDecimal filterBox(List<GoodsBoxDetail> downloadBoxList, BigDecimal sortWeight, GoodsBoxDetail goodsBoxDetail, String clientId, SortTaskDetail sortTaskDetail) {
        BigDecimal remainingWeight = sortWeight.subtract(goodsBoxDetail.getWeight());
        //标品，货箱内剩余重量少于其一半，算是空箱，如：标品一斤，货箱内剩余重量，小于半斤算是空箱,或者用货箱误差，到时再看，
        // 若是一份一份拿，用货箱误差还是标品重量的一半，应该差不多，试运营看情况再调整
        boolean remain = remainingWeight.abs().compareTo(sortTaskDetail.getWeight().multiply(Constants.HALF_PERCENT).setScale(2, RoundingMode.HALF_UP)) < 1;
        if (!goodsBoxDetail.isInBulk() && remain) {
            remainingWeight = BigDecimal.ZERO;
        }
        if (remainingWeight.compareTo(BigDecimal.ZERO) <= 0) {
            //一个货箱详情能满足分拣重量
            downloadBoxList.add(goodsBoxDetail);
            goodsBoxDetail.setWeight(remainingWeight.abs());
            redisCache.set(CacheConstants.SORT_TASK_DOWNLOAD_WEIGHT + clientId + ":" + goodsBoxDetail.getBoxCode(), sortWeight, 1, TimeUnit.DAYS);
            return null;
        } else {
            //计算当前货箱可分拣份数，取整
            sortTaskDetail.setCountQuantity(sortTaskDetail.getCountQuantity() - goodsBoxDetail.getWeight().divide(sortTaskDetail.getWeight(), 0, RoundingMode.HALF_UP).intValue());
            sortWeight = remainingWeight;
            downloadBoxList.add(goodsBoxDetail);
            goodsBoxDetail.setWeight(BigDecimal.ZERO);
            redisCache.set(CacheConstants.SORT_TASK_DOWNLOAD_WEIGHT + clientId + ":" + goodsBoxDetail.getBoxCode(), sortWeight, 1, TimeUnit.DAYS);
        }

        return sortWeight;
    }

    /**
     * 分拣加工任务，按原料分拣
     *
     * @param clientId
     * @param status
     * @param spuId
     * @return
     * @author yw
     * @date 2022-04-17 15:42:02
     */
    @Override
    public SortTaskDto sortingTask(String clientId, String status, String spuId, Param param) throws Exception {
        //todo 目前是按单个产品按保质期出库，若一个箱子有两个原料都要分拣的，且另一个原料保质期也是要下，目前只会先根据查询条件的spuId分拣
        //todo 另一个原料可能下另一个保质期长点的箱子，后期优化称当前箱子原料
        //todo 如：订单有白菜萝卜要分拣，根据白菜spu查询保质期最短的箱子，刚好此箱子包含也是保质期最短的萝卜，当前的处理方式是，先分拣白菜
        //todo 白菜不够再下箱子分拣白菜，若是白菜够了，货箱直接上架，不分拣萝卜，萝卜的分拣则是出库一个离此箱子保质期最近的萝卜箱子，分拣完
        //todo 新下的萝卜的箱子，再发送指令下其他萝卜箱子，这时候会出库萝卜跟白菜混合的箱子
        //todo 后期可优化成若是萝卜白菜都是保质期最近过期的，可以把混合箱子的萝卜白菜一起分拣
        if (StrUtil.isEmpty(clientId)) {
            throw new UtilException("参数为空");
        }
        if (StrUtil.isEmpty(status)) {
            status = Constants.SORT_TASK_DETAIL_ING;
        }
        SortTaskDto sortTaskDto = new SortTaskDto();
        //当前操作员分拣的任务详情
        /**查询正在分拣任务，和下架都会调用到sortingTask方法，在此区分是否由下架任务，
         * 若是没有分拣任务，则不下箱子，只单纯作为web的正在分拣任务的查询，当有分拣任务的时候，则分配货箱出库分拣**/
        List<SortTaskDetail> list = sortTaskDetailService.selectByClient(null, status, spuId, null, param.getBigProduct());
        List<SortTaskDetail> sortTaskDetailList = new ArrayList<>();
        List<SortTaskDetail> reSortTaskDetailList = new ArrayList<>();
        if (!list.isEmpty()) {
            for (SortTaskDetail sortTaskDetail : list) {
                if (sortTaskDetail.getStatus().equals(Constants.SORT_TASK_DETAIL_WART)) {
                    reSortTaskDetailList.add(sortTaskDetail);
                } else {
                    sortTaskDetailList.add(sortTaskDetail);
                }
            }
        }
        sortTaskDto.setSortDetailList(list);
        sortTaskDto.setReSortDetailList(reSortTaskDetailList);
        return sortTaskDto;
    }

    /**
     * 发送下架信息
     * 同一个分拣任务，多个货箱编号连续传入wcs
     * 只发一次下架任务，剩余到响应处发送
     *
     * @param clientId
     * @param status
     * @return void
     * @author yw
     * @date 2022-06-21 15:00:11
     */
    @Override
    public synchronized void downloadBox(String clientId, String status, SortTaskDto sortTaskDto) throws Exception {
        //todo 同一个分拣任务，多个货箱编号连续传入wcs
        List<GoodsBoxDetail> boxList = selectUniqueBoxCodeBySpuList(clientId, status, sortTaskDto);
        log.error("downloadBox:{}", JSONUtil.toJsonStr(boxList));
        if (boxList != null && !boxList.isEmpty()) {

            //分拣发指令，所有指令作为一个批次，wcs按批次内顺序执行
            Object takeNum = redisCache.get(CacheConstants.SORT_TASK_TAKE_NUM + clientId);
            if (takeNum == null) {
                String num = IdUtil.getSnowflake().nextIdStr();
                redisCache.set(CacheConstants.SORT_TASK_TAKE_NUM + clientId, num, 1, TimeUnit.HOURS);
            }
            //根据仓库号分组，然后设置到redis缓存中
            Map<Integer, List<GoodsBoxDetail>> numberList = boxList.stream().collect(Collectors.groupingBy(GoodsBoxDetail::getWarehouseNumber, LinkedHashMap::new, Collectors.toList()));
            List<Object> o = redisCache.getList(CacheConstants.SORT_TASK_DOWNLOAD + clientId, 0, -1);
            if (o != null && !o.isEmpty()) {
                /**当redis存在当前用户下架货箱列表，则判断新列表stringList是否有不存在已存放在redis列表中的数据，有则更新到redis列表中**/
                Map<String, String> map = o.stream().collect(Collectors.toMap(item -> String.valueOf(item), item -> item.toString(), (key1, key2) -> key1));
                numberList.forEach((key, value) -> {
                    //温区排序，冷冻区后下
                    List<GoodsBoxDetail> boxDetailListInfo = value.stream().sorted(Comparator.comparing(GoodsBoxDetail::getActTemperatureCode).reversed()).collect(Collectors.toList());
                    List<Object> stringList = boxDetailListInfo.stream().map(GoodsBoxDetail::getBoxCode).collect(Collectors.toList());
                    if (stringList != null && !stringList.isEmpty()) {
                        List<Object> newList = new ArrayList<>();
                        for (int i = 0; i < stringList.size(); i++) {
                            String tmp = String.valueOf(stringList.get(i));
                            if (!map.containsKey(tmp)) {
                                newList.add(tmp);
                            }
                        }
                        if (!newList.isEmpty()) {
                            redisCache.setAllListOfRight(CacheConstants.SORT_TASK_DOWNLOAD + key, newList);
                        }
                    }
                });
            } else {
//                Map<String, SysDictData> map = list.stream().collect(Collectors.toMap(SysDictData::getDictLabel, Function.identity(), (key1, key2) -> key2));
                //按仓库号设置下架货箱到缓存
                numberList.forEach((key, value) -> {
                    //温区排序，冷冻区后下
                    List<GoodsBoxDetail> boxDetailListInfo = value.stream().sorted(Comparator.comparing(GoodsBoxDetail::getActTemperatureCode).reversed()).collect(Collectors.toList());
                    List<Object> boxCodeList = boxDetailListInfo.stream().map(GoodsBoxDetail::getBoxCode).collect(Collectors.toList());
                    redisCache.setAllListOfRight(CacheConstants.SORT_TASK_DOWNLOAD + key, boxCodeList);
                });
            }

            ThreadUtil.execAsync(() -> {
                sortDownload(numberList);
            });
        }
    }

    /**
     * 分拣下架
     *
     * @param numberList
     * @return void
     * @author yw
     * @date 2023-11-07 11:27:02
     */
    public void sortDownload(Map<Integer, List<GoodsBoxDetail>> numberList) {
        //三个仓库下架
        for (Map.Entry<Integer, List<GoodsBoxDetail>> entry : numberList.entrySet()) {
            Integer key = entry.getKey();
            log.error("分拣下架仓库号：{}", key);
            Param param = new Param();
            param.setUpOrDownPolicy(new UpOrDownPolicy());
            param.getUpOrDownPolicy().setClientId(String.valueOf(key));
            //校验，分拣连续发时，且触发货箱离开库位流程，货箱
            try {
                //连续发，wcs处理不过来，间隔发送
                wcsService.sortingOutBatch(param, false);
                //只发一次，需等wcs响应才能再发
                break;
            } catch (Exception e) {
                throw new UtilException("downloadBox下架异常：{}", e);
            }
        }
    }

    /**
     * 校验分拣是否结束，结束则更改状态
     *
     * @param clientId
     * @param status
     * @return
     * @author yw
     * @date 2022-04-17 15:42:02
     */
    @Override
    public void sortDone(String clientId, String status) throws Exception {
        if (StrUtil.isEmpty(clientId) || StrUtil.isEmpty(status)) {
            throw new UtilException("参数为空");
        }
        List<SortTask> list = super.list(Wrappers.<SortTask>lambdaQuery().eq(SortTask::getStatus, status));
        list.forEach(item -> {
            List<SortTaskDetail> detailList = sortTaskDetailService.list(Wrappers.<SortTaskDetail>lambdaQuery().eq(SortTaskDetail::getSortTaskId, item.getId()).eq(SortTaskDetail::getStatus, Constants.SORT_TASK_DETAIL_ING));
            if (detailList.isEmpty()) {
                item.setStatus(Constants.SORT_TASK_STATUS_DONE);
                sortTaskMapper.updateById(item);
            }
        });
    }
}